/******************************************************************************
	Cobalt - utility libraries for building XNA games
	Copyright (C) 2008 Philip Djonov

	This program is free software; you can redistribute it and/or modify it
	under the terms of the GNU General Public License as published by the Free
	Software Foundation; either version 2 of the License, or (at your option)
	any later version.

	This program is distributed in the hope that it will be useful, but
	WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
	or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
	for more details.

	You should have received a copy of the GNU General Public License along
	with this program; if not, write to the
	
		Free Software Foundation, Inc.
		51 Franklin Street, Fifth Floor
		Boston, MA  02110-1301, USA.
******************************************************************************/

using System;
using System.Collections.Generic;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace Cobalt.Framework.Graphics
{
	public class Light
	{
	}

	public sealed class ShadingContext
	{
		private float time;
		public float RenderTime { get { return time; } set { time = value; } }

		#region Transformation State
		private Matrix model = Matrix.Identity;
		public Matrix Model
		{
			get { return model; }
			set
			{
				model = value;
				mvIsValid = false;
				mvpIsValid = false;
				mInvIsValid = false;

				matrixCounter = unchecked( matrixCounter + 1 );
			}
		}

		private bool mInvIsValid = false;
		private Matrix modelInverse;
		public Matrix ModelInverse
		{
			get
			{
				if( !mInvIsValid )
				{
					Matrix.Invert( ref model, out modelInverse );
					mInvIsValid = true;
				}

				return modelInverse;
			}
		}

		private Matrix view = Matrix.CreateLookAt( new Vector3( 10, 0, 10 ), Vector3.Zero, new Vector3( 0, 1, 0 ) );
		public Matrix View
		{
			get { return view; }
			set
			{
				view = value;

				vInvIsValid = false;
				mvIsValid = false;
				mvpIsValid = false;
				vpIsValid = false;

				matrixCounter = unchecked( matrixCounter + 1 );
			}
		}

		private bool vInvIsValid = false;
		private Matrix viewInverse;
		public Matrix ViewInverse
		{
			get
			{
				if( !vInvIsValid )
				{
					Matrix.Invert( ref view, out viewInverse );
					vInvIsValid = true;
				}

				return viewInverse;
			}
		}

		/// <summary>
		/// The camera position in world space.
		/// </summary>
		public Vector3 WorldCameraPosition
		{
			get
			{
				Matrix vInv = ViewInverse;
				return new Vector3( vInv.M41, vInv.M42, vInv.M43 );
			}
		}

		public Vector3 ModelCameraPosition
		{
			get
			{
				Matrix mInv = ModelInverse;
				Vector3 worldPos = WorldCameraPosition;
				Vector3 ret;
				Vector3.Transform( ref worldPos, ref mInv, out ret );
				return ret;
			}
		}

		private bool mvIsValid = false;
		private Matrix modelView;
		public Matrix ModelView
		{
			get
			{
				if( !mvIsValid )
				{
					modelView = model * view;
					mvIsValid = true;
				}

				return modelView;
			}
		}

		private Matrix projection = Matrix.CreatePerspectiveFieldOfView( MathHelper.ToRadians( 90 ), 800.0F / 600.0F, 0.1F, 1000.0F );
		public Matrix Projection
		{
			get { return projection; }
			set
			{
				projection = value;
				
				mvpIsValid = false;
				vpIsValid = false;

				matrixCounter = unchecked( matrixCounter + 1 );
			}
		}

		private bool mvpIsValid = false;
		private Matrix modelViewProjection;
		public Matrix ModelViewProjection
		{
			get
			{
				if( !mvpIsValid )
				{
					modelViewProjection = ModelView * projection;
					mvpIsValid = true;
				}

				return modelViewProjection;
			}
		}

		private bool vpIsValid = false;
		private Matrix viewProjection;
		private BoundingFrustum frustum = new BoundingFrustum( Matrix.Identity );

		private void EnsureViewProjectionState()
		{
			if( !vpIsValid )
			{
				viewProjection = view * projection;
				frustum.Matrix = viewProjection;

				vpIsValid = true;
			}
		}

		public Matrix ViewProjection
		{
			get
			{
				EnsureViewProjectionState();
				return viewProjection;
			}
		}

		public BoundingFrustum Frustum
		{
			get
			{
				EnsureViewProjectionState();
				return frustum;
			}
		}

		private Vector2 depthRange = new Vector2( 0, 50 );
		public Vector2 DepthRange
		{
			get { return depthRange; }
			set { depthRange = value; }
		}

		/// <summary>
		/// This number is incremented any time a matrix changes.
		/// </summary>
		internal uint matrixCounter;

		#endregion

		internal Material currentMaterial;
		public Material CurrentMaterial { get { return currentMaterial; } }

		internal LayerShader currentMaterialLayer;
		public LayerShader CurrentMaterialLayer { get { return currentMaterialLayer; } }

		private List<Light> lights = new List<Light>( 64 );
		public List<Light> Lights
		{
			get { return lights; }
		}

		#region Graphics Resources
		private GraphicsDevice graphicsDevice;
		public GraphicsDevice GraphicsDevice
		{
			get { return graphicsDevice; }
		}
		#endregion

		private Material overrideMaterial;
		public Material OverrideMaterial { get { return overrideMaterial; } set { overrideMaterial = value; } }

		private int shadingComplexityRank;
		public int ShadingComplexityRank
		{
			get { return shadingComplexityRank; }
			set
			{
				if( value < 0 )
					value = 0;
				shadingComplexityRank = value;
			}
		}

		private PerformanceCounters counters = new PerformanceCounters();
		public PerformanceCounters PerformanceCounters
		{
			get { return counters; }
		}

		public ShadingContext( GraphicsDevice graphicsDevice )
		{
			this.graphicsDevice = graphicsDevice;
		}
	}

	public sealed class PerformanceCounters
	{
		public PerformanceCounters()
		{
		}

		internal int layerBegins;
		internal int layerPassBegins;
		internal int surfacesDrawn;
		internal int surfacesDrawCount;

		private int drawPrimsCount;
		private int verticesDrawn;
		private int primitivesDrawn;

		public void UpdateGeometryCounters( int numVerts, int numPrims, int drawPrimsCount )
		{
			if( numVerts < 0 || numPrims < 0 )
				throw new ArgumentOutOfRangeException();

			this.verticesDrawn += numVerts;
			this.primitivesDrawn += numPrims;
			this.drawPrimsCount += drawPrimsCount;
		}

		public void Reset()
		{
			layerBegins = 0;
			layerPassBegins = 0;
			surfacesDrawn = 0;
			surfacesDrawCount = 0;
			verticesDrawn = 0;
			primitivesDrawn = 0;
			drawPrimsCount = 0;
		}

		/// <summary>
		/// Gets the number of times <see cref="LayerShader.Begin"/> is called.
		/// </summary>
		public int LayerBegins { get { return layerBegins; } }
		/// <summary>
		/// Gets the number of times <see cref="LayerShader.BeginPass"/> is called.
		/// </summary>
		public int LayerPassBegins { get { return layerPassBegins; } }
		/// <summary>
		/// Gets the number of surfaces that have been drawn.
		/// </summary>
		public int SurfacesDrawn { get { return surfacesDrawn; } }
		/// <summary>
		/// Gets the number of times a surface has actually been been rendered.
		/// </summary>
		/// <remarks>
		/// The difference between this and <see cref="SurfacesDrawn"/> is that
		/// this will be incremented multiple time during the rendering of a multi-pass
		/// shading layer, whereas <c>SurfacesDrawn</c> will not.
		/// </remarks>
		public int SurfacesDrawCount { get { return surfacesDrawCount; } }

		/// <summary>
		/// Gets the number of vertices that have been drawn this frame.
		/// </summary>
		public int VerticesDrawn { get { return verticesDrawn; } }
		/// <summary>
		/// Gets the number of primitives that have been drawn this frame.
		/// </summary>
		public int PrimitivesDrawn { get { return primitivesDrawn; } }
		/// <summary>
		/// Gets the number of times one the <see cref="GraphicsDevice"/>'s
		/// <c>DrawPrimitives</c> methods have been called.
		/// </summary>
		public int DrawPrimitivesCount { get { return drawPrimsCount; } }
	}

}